{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b518b04cbfe0"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "906e07f6e562"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8bd329a4bbca"
      },
      "source": [
        "# Masking and padding with Keras"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8b208d0913b8"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/masking_and_padding\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/masking_and_padding.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/masking_and_padding.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/keras/masking_and_padding.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8d4ac441b1fc"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ec52be14e686"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3731f3ff3670"
      },
      "source": [
        "## 시작하기\n",
        "\n",
        "**마스킹** 은 시퀀스 처리 레이어에 입력의 특정 시간 단계가 누락되어 데이터를 처리 할 때 건너 뛰도록 지시하는 방법입니다.\n",
        "\n",
        "**패딩**은 마스킹된 스텝이 시퀀스의 시작 또는 도입부에 위치하는 특수한 형태의 마스킹입니다. 패딩이 필요한 이유는 시퀀스 데이터를 연속 배치로 인코딩해야 하는 데 있습니다. 배치의 모든 시퀀스를 지정된 표준 길이에 맞추려면 일부 시퀀스를 패딩 처리하거나 잘라내야 합니다.\n",
        "\n",
        "자세히 살펴 보자."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ac6b121d6be0"
      },
      "source": [
        "## 패딩 시퀀스 데이터\n",
        "\n",
        "시퀀스 데이터를 처리 할 때 개별 샘플의 길이가 다른 것이 매우 일반적입니다. 다음 예제를 고려하십시오 (텍스트로 단어로 토큰 화됨).\n",
        "\n",
        "```\n",
        "[\n",
        "  [\"Hello\", \"world\", \"!\"],\n",
        "  [\"How\", \"are\", \"you\", \"doing\", \"today\"],\n",
        "  [\"The\", \"weather\", \"will\", \"be\", \"nice\", \"tomorrow\"],\n",
        "]\n",
        "```\n",
        "\n",
        "어휘 조회 후 데이터는 다음과 같이 정수로 벡터화 될 수 있습니다.\n",
        "\n",
        "```\n",
        "[\n",
        "  [71, 1331, 4231]\n",
        "  [73, 8, 3215, 55, 927],\n",
        "  [83, 91, 1, 645, 1253, 927],\n",
        "]\n",
        "```\n",
        "\n",
        "데이터는 개별 샘플의 길이가 각각 3, 5 및 6인 중첩된 목록입니다. 딥 러닝 모델의 입력 데이터는 단일 텐서(이 경우, 예를 들어 `(batch_size, 6, vocab_size)` 형상의 텐서)여야 하므로 가장 긴 항목보다 짧은 샘플은 일부 자리 표시자 값으로 패딩 처리해야 합니다(또는, 짧은 샘플을 패딩 처리하기 전에 긴 샘플을 잘라낼 수도 있음).\n",
        "\n",
        "Keras는 Python 목록을 잘라서 공통 길이로 패딩 처리하는 유틸리티 기능을 제공합니다: `tf.keras.preprocessing.sequence.pad_sequences`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bb64fb185a05"
      },
      "outputs": [],
      "source": [
        "raw_inputs = [\n",
        "    [711, 632, 71],\n",
        "    [73, 8, 3215, 55, 927],\n",
        "    [83, 91, 1, 645, 1253, 927],\n",
        "]\n",
        "\n",
        "# By default, this will pad using 0s; it is configurable via the\n",
        "# \"value\" parameter.\n",
        "# Note that you could \"pre\" padding (at the beginning) or\n",
        "# \"post\" padding (at the end).\n",
        "# We recommend using \"post\" padding when working with RNN layers\n",
        "# (in order to be able to use the\n",
        "# CuDNN implementation of the layers).\n",
        "padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(\n",
        "    raw_inputs, padding=\"post\"\n",
        ")\n",
        "print(padded_inputs)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "03092b2da690"
      },
      "source": [
        "## 마스킹\n",
        "\n",
        "이제 모든 샘플의 길이가 균일하므로 데이터의 일부가 실제로 채워져 있다는 사실을 모델에 알려야합니다. 그 메커니즘은 **마스킹** 입니다.\n",
        "\n",
        "Keras 모델에서 입력 마스크를 도입하는 세 가지 방법이 있습니다.\n",
        "\n",
        "- `keras.layers.Masking` 레이어를 추가하십시오.\n",
        "- `mask_zero=True` 로 `keras.layers.Embedding` 레이어를 구성하십시오.\n",
        "- 이 인수를 지원하는 계층 (예 : RNN 계층)을 호출 할 때 `mask` 인수를 수동으로 전달하십시오."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6103601e5fff"
      },
      "source": [
        "## 마스크 생성 레이어 : `Embedding` 및 `Masking`\n",
        "\n",
        "후드 아래에서이 레이어는 마스크 텐서 (모양 `(batch, sequence_length)` 가진 2D 텐서)를 만들어 `Masking` 또는 `Embedding` 레이어에서 반환 한 텐서 출력에 연결합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b2363b293483"
      },
      "outputs": [],
      "source": [
        "embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)\n",
        "masked_output = embedding(padded_inputs)\n",
        "\n",
        "print(masked_output._keras_mask)\n",
        "\n",
        "masking_layer = layers.Masking()\n",
        "# Simulate the embedding lookup by expanding the 2D input to 3D,\n",
        "# with embedding dimension of 10.\n",
        "unmasked_embedding = tf.cast(\n",
        "    tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 10]), tf.float32\n",
        ")\n",
        "\n",
        "masked_embedding = masking_layer(unmasked_embedding)\n",
        "print(masked_embedding._keras_mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "17e4bdb563b2"
      },
      "source": [
        "인쇄 된 결과에서 볼 수 있듯이 마스크는 모양이 `(batch_size, sequence_length)` 인 2D 부울 텐서이며, 각 개별 `False` 항목은 처리 중에 해당 시간 단계를 무시해야 함을 나타냅니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cf11a0399fcf"
      },
      "source": [
        "## Functional API 및 Sequential API의 마스크 전파\n",
        "\n",
        "Functional API 또는 Sequential API를 사용하는 경우 `Embedding` 또는 `Masking` 계층에서 생성 된 마스크는이를 사용할 수있는 계층 (예 : RNN 계층)에 대해 네트워크를 통해 전파됩니다. Keras는 입력에 해당하는 마스크를 자동으로 가져 와서 사용 방법을 알고있는 레이어로 전달합니다.\n",
        "\n",
        "예를 들어, 다음 순차 모델에서 `LSTM` 레이어는 마스크를 자동으로 수신하므로 패딩 처리된 값을 무시합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0adbecda288a"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential(\n",
        "    [layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True), layers.LSTM(32),]\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a8ac6481a1d5"
      },
      "source": [
        "다음과 같은 기능적 API 모델의 경우에도 마찬가지입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f374ab06743d"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)\n",
        "outputs = layers.LSTM(32)(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2f2c4b96ecb5"
      },
      "source": [
        "## 마스크 텐서를 레이어로 직접 전달"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "11dccb581014"
      },
      "source": [
        "마스크를 처리 할 수있는 레이어 (예 : `LSTM` 레이어)는 `__call__` 메서드에 `mask` 인수가 있습니다.\n",
        "\n",
        "한편 마스크 (예 : `Embedding` )를 생성하는 레이어는 호출 할 수있는 `compute_mask(input, previous_mask)` 메소드를 노출합니다.\n",
        "\n",
        "따라서 마스크 생성 레이어의 `compute_mask()` 메서드 출력을 다음과 같이 마스크 소비 레이어의 `__call__` 메서드로 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1955aa63896b"
      },
      "outputs": [],
      "source": [
        "class MyLayer(layers.Layer):\n",
        "    def __init__(self, **kwargs):\n",
        "        super(MyLayer, self).__init__(**kwargs)\n",
        "        self.embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)\n",
        "        self.lstm = layers.LSTM(32)\n",
        "\n",
        "    def call(self, inputs):\n",
        "        x = self.embedding(inputs)\n",
        "        # Note that you could also prepare a `mask` tensor manually.\n",
        "        # It only needs to be a boolean tensor\n",
        "        # with the right shape, i.e. (batch_size, timesteps).\n",
        "        mask = self.embedding.compute_mask(inputs)\n",
        "        output = self.lstm(x, mask=mask)  # The layer will ignore the masked values\n",
        "        return output\n",
        "\n",
        "\n",
        "layer = MyLayer()\n",
        "x = np.random.random((32, 10)) * 100\n",
        "x = x.astype(\"int32\")\n",
        "layer(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b04dd330f848"
      },
      "source": [
        "## 사용자 정의 레이어에서 마스킹 지원"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8451a1a8ff27"
      },
      "source": [
        "때로는 마스크를 생성하는 레이어(예: `Embedding`) 또는 현재 마스크를 수정해야 하는 레이어를 작성해야 할 수도 있습니다.\n",
        "\n",
        "예를 들어, 시간 차원에서 연결된 `Concatenate` 레이어와 같이 입력과 다른 시간 차원을 가진 텐서를 생성하는 레이어는 다운스트림 레이어가 마스킹된 타임스텝을 올바르게 처리할 수 있도록 현재 마스크를 수정해야 합니다.\n",
        "\n",
        "To do this, your layer should implement the `layer.compute_mask()` method, which produces a new mask given the input and the current mask.\n",
        "\n",
        "Here is an example of a `TemporalSplit` layer that needs to modify the current mask."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a06fb2194c0d"
      },
      "outputs": [],
      "source": [
        "class TemporalSplit(keras.layers.Layer):\n",
        "    \"\"\"Split the input tensor into 2 tensors along the time dimension.\"\"\"\n",
        "\n",
        "    def call(self, inputs):\n",
        "        # Expect the input to be 3D and mask to be 2D, split the input tensor into 2\n",
        "        # subtensors along the time axis (axis 1).\n",
        "        return tf.split(inputs, 2, axis=1)\n",
        "\n",
        "    def compute_mask(self, inputs, mask=None):\n",
        "        # Also split the mask into 2 if it presents.\n",
        "        if mask is None:\n",
        "            return None\n",
        "        return tf.split(mask, 2, axis=1)\n",
        "\n",
        "\n",
        "first_half, second_half = TemporalSplit()(masked_embedding)\n",
        "print(first_half._keras_mask)\n",
        "print(second_half._keras_mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "282b867dcd95"
      },
      "source": [
        "입력 값에서 마스크를 생성 할 수있는 `CustomEmbedding` 레이어의 다른 예는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "e760655cd39c"
      },
      "outputs": [],
      "source": [
        "class CustomEmbedding(keras.layers.Layer):\n",
        "    def __init__(self, input_dim, output_dim, mask_zero=False, **kwargs):\n",
        "        super(CustomEmbedding, self).__init__(**kwargs)\n",
        "        self.input_dim = input_dim\n",
        "        self.output_dim = output_dim\n",
        "        self.mask_zero = mask_zero\n",
        "\n",
        "    def build(self, input_shape):\n",
        "        self.embeddings = self.add_weight(\n",
        "            shape=(self.input_dim, self.output_dim),\n",
        "            initializer=\"random_normal\",\n",
        "            dtype=\"float32\",\n",
        "        )\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.nn.embedding_lookup(self.embeddings, inputs)\n",
        "\n",
        "    def compute_mask(self, inputs, mask=None):\n",
        "        if not self.mask_zero:\n",
        "            return None\n",
        "        return tf.not_equal(inputs, 0)\n",
        "\n",
        "\n",
        "layer = CustomEmbedding(10, 32, mask_zero=True)\n",
        "x = np.random.random((3, 10)) * 9\n",
        "x = x.astype(\"int32\")\n",
        "\n",
        "y = layer(x)\n",
        "mask = layer.compute_mask(x)\n",
        "\n",
        "print(mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bb34149eb837"
      },
      "source": [
        "## 호환 가능한 레이어에서 전파를 마스크하도록 선택\n",
        "\n",
        "대부분의 레이어는 시간 차원을 수정하지 않으므로 현재 마스크를 수정할 필요가 없습니다. 그러나 그들은 여전히 현재 마스크를 변경하지 않고 다음 레이어로 **전파** 할 수 있기를 원할 수 있습니다. **이것은 옵트 인 동작입니다.** 기본적으로 사용자 정의 레이어는 현재 마스크를 제거합니다 (프레임 워크에서 마스크 전파가 안전한지 여부를 알 수있는 방법이 없기 때문에).\n",
        "\n",
        "시간 차원을 수정하지 않는 사용자 정의 레이어가 있고 현재 입력 마스크를 전파 할 수 있으려면 레이어 생성자에서 `self.supports_masking = True` 를 설정해야합니다. 이 경우 `compute_mask()` 의 기본 동작은 현재 마스크를 통과하는 것입니다.\n",
        "\n",
        "마스크 전파가 허용 된 레이어의 예는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "895c35534d06"
      },
      "outputs": [],
      "source": [
        "class MyActivation(keras.layers.Layer):\n",
        "    def __init__(self, **kwargs):\n",
        "        super(MyActivation, self).__init__(**kwargs)\n",
        "        # Signal that the layer is safe for mask propagation\n",
        "        self.supports_masking = True\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.nn.relu(inputs)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a2e1e0a81995"
      },
      "source": [
        "이제 마스크를 생성하는 레이어(예: `Embedding`)와 마스크를 소비하는 레이어(예: `LSTM`) 사이에서 이 사용자 정의 레이어를 사용하여 마스크 소비 레이어까지 마스크를 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "486e39e9a9a7"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)\n",
        "x = MyActivation()(x)  # Will pass the mask along\n",
        "print(\"Mask found:\", x._keras_mask)\n",
        "outputs = layers.LSTM(32)(x)  # Will receive the mask\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "55ab9c02f4ba"
      },
      "source": [
        "## 마스크 정보가 필요한 레이어 작성\n",
        "\n",
        "일부 레이어는 마스크 *소비자*입니다. 이러한 레이어는 `call`에서 `mask` 인수를 허용하고 이를 사용하여 특정 타임스텝을 건너뛸지 여부를 결정합니다.\n",
        "\n",
        "이러한 계층을 작성하려면 `call` 서명에 `mask=None` 인수를 추가하면됩니다. 입력과 관련된 마스크는 가능할 때마다 레이어로 전달됩니다.\n",
        "\n",
        "다음은 간단한 예입니다. 마스크 된 시간 단계를 버리고 입력 시퀀스의 시간 차원 (축 1)에 대한 소프트 맥스를 계산하는 레이어입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7809df539882"
      },
      "outputs": [],
      "source": [
        "class TemporalSoftmax(keras.layers.Layer):\n",
        "    def call(self, inputs, mask=None):\n",
        "        broadcast_float_mask = tf.expand_dims(tf.cast(mask, \"float32\"), -1)\n",
        "        inputs_exp = tf.exp(inputs) * broadcast_float_mask\n",
        "        inputs_sum = tf.reduce_sum(inputs * broadcast_float_mask, axis=1, keepdims=True)\n",
        "        return inputs_exp / inputs_sum\n",
        "\n",
        "\n",
        "inputs = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "x = layers.Embedding(input_dim=10, output_dim=32, mask_zero=True)(inputs)\n",
        "x = layers.Dense(1)(x)\n",
        "outputs = TemporalSoftmax()(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "y = model(np.random.randint(0, 10, size=(32, 100)), np.random.random((32, 100, 1)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6373f43bbe18"
      },
      "source": [
        "## 요약\n",
        "\n",
        "이것이 Keras의 패딩 및 마스킹에 대해 알아야 할 전부입니다. 요약하자면:\n",
        "\n",
        "- \"Masking\"은 레이어가 시퀀스 입력에서 특정 시간 단계를 건너 뛰거나 무시할 때를 알 수있는 방법입니다.\n",
        "- 일부 레이어는 마스크 생성기입니다. `Embedding` 하면 입력 값에서 마스크를 생성 할 수 있으며 ( `mask_zero=True` ) `Masking` 레이어도 생성 할 수 있습니다.\n",
        "- 일부 레이어는 마스크 소비자입니다. `__call__` 메서드에 `mask` 인수를 표시합니다. 이것은 RNN 계층의 경우입니다.\n",
        "- Functional API 및 Sequential API에서 마스크 정보는 자동으로 전파됩니다.\n",
        "- 독립형 방식으로 레이어를 사용하는 경우 `mask` 인수를 레이어에 수동으로 전달할 수 있습니다.\n",
        "- 현재 마스크를 수정하거나 새 마스크를 생성하거나 입력과 관련된 마스크를 사용하는 레이어를 쉽게 작성할 수 있습니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "masking_and_padding.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
